/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System;
using NTasking.ValidationTests.ACATS_3_0.Support;
using NUnit.Framework;
using Rhino.Mocks;

namespace NTasking.ValidationTests.ACATS_3_0.C9
{

    [TestFixture(Description = "")]
    public class C940001 : ValidationTest
    {

        //      Declare a protected type with two entries.  A task may call the Take
        //      entry to get a token which allows it to continue processing.  If it 
        //      has the token, it may call the Give entry to return it.  The tasks 
        //      implement a discipline whereby only the task with the token may be 
        //      active.  The test does not require any specific order for the tasks 
        //      to run.

        const bool TrueToken = true;
        const bool InitToken = false;

        private static TokenManager sequenceManager;
        private static TcArtifact tcArtifact;
        private static IValidationTestResult result;


        private static void Bump(ref int item)
        {
            item += 1;

            if (item > 2)
            {
                Console.WriteLine("Incremented without corresponding decrement");
                result.TestFailed();
            }
        }


        private static void Decrement(ref int item)
        {
            item -= 1;

            if (item < 1)
            {
                Console.WriteLine("Decremented without corresponding increment");
                result.TestFailed();
            }
        }


        private class TokenManager : Protected
        {

            private bool Token = TrueToken;


            [Entry]
            internal Func<bool> Take { get; private set; }


            [Entry]
            internal Func<bool, bool> Give { get; private set; }


            public TokenManager()
            {
                // Calling task will Take the token, so check first that token_mgr owns the
                // token to give, then give it to caller
                Entry(Take,
                    When: () => Token,
                Is: () =>
                {
                    bool result = Token;
                    Token = false;
                    return result;
                });

                // Calling task will Give the token back, so first check that token_mgr does not
                // own the token, then check that the task has, the token to give, then take it from the
                // task.
                // If caller does not own the token, then it falls out of the entry body with no
                // action.
                Entry(Give,
                    When: () => !Token,
                Is: (t) =>
                {
                    if (t)
                        Token = t;

                    return false;
                });
            }

        }


        private struct TcArtifact
        {
            internal int TcInt;

            internal int NumberOfAccess;


            internal TcArtifact(int tcInt, int nb)
            {
                TcInt = tcInt;
                NumberOfAccess = nb;
            }
        }


        private class NetworkNode : ValidationTestTask
        {

            private bool SlotForToken = InitToken;


            internal NetworkNode(IValidationTestResult result)
                : base(result)
            {
            }


            protected override void Body()
            {
                // Ask for token - if request is not granted, task will be queued
                SlotForToken = sequenceManager.Take();

                // Task now has token and may perform its work

                //==========================//
                // in this case, the work is to ensure that the test results
                // are the expected ones!
                //==========================//
                Bump(ref tcArtifact.TcInt);
                tcArtifact.NumberOfAccess += 1;
                if (!SlotForToken)
                {
                    Console.WriteLine("Incorrect results from entry Take");
                    result.TestFailed();
                }

                // give a chance for other tasks to (incorrectly) run
                Ada.Delay(Impdef.MinimumTaskSwitch);

                // prepare to return token
                Decrement(ref tcArtifact.TcInt);

                // Task has completed its work and will return token

                // return token to sequence manager
                SlotForToken = sequenceManager.Give(SlotForToken);

                if (SlotForToken)
                {
                    Console.WriteLine("Incorrect results from entry Give");
                    result.TestFailed();
                }
            }
            protected override void Exception(Exception ex)
            {
                Console.WriteLine("Unexpected exception raised in task");
                Console.WriteLine(ex);
                Result.TestFailed();
            }
        }


        [Test(Description =
            "Check that a protected object provides coordinated access to " +
            "shared data.  Check that it can be used to sequence a number of tasks." +
            "Use the protected object to control a single token for which three " +
            "tasks compete.  Check that only one task is running at a time and that" +
            "all tasks get a chance to run sometime.")]
        public void Test()
        {
            result = Mocks.DynamicMock<IValidationTestResult>();

            Ada.Declare(() =>
            {
                sequenceManager = new TokenManager();
                tcArtifact = new TcArtifact(1, 0);

                new NetworkNode(result);
                new NetworkNode(result);
                new NetworkNode(result);

                // Setup text expectations

                Expect.Call(result.TestFailed).Repeat.Never();
                Mocks.ReplayAll();

                // Start test execution
            },
            Begin: () =>
            {
            }); // wait for all tasks to terminate before reporting result

            if (tcArtifact.NumberOfAccess != 3)
            {
                Console.WriteLine("Not all tasks got through");
                result.TestFailed();
            }
        }

    }

}
